package com.jgp.swagger.config;

import com.fasterxml.classmate.ResolvedType;
import com.fasterxml.classmate.TypeResolver;
import com.google.common.base.Predicate;
import com.jgp.swagger.props.SwaggerConfigPropBean;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.web.bind.annotation.RestController;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.schema.AlternateTypeRule;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

import javax.annotation.PostConstruct;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

import static com.google.common.base.Predicates.or;
import static springfox.documentation.builders.PathSelectors.regex;

/**
 * 项目   jgp-cloud-parent
 * 作者   loufei
 * 时间   2019/3/25
 */
@Order(4)
@Configuration
@EnableSwagger2
@ConditionalOnProperty(prefix = "jgp.api-doc", name = "enabled", matchIfMissing = true)
@EnableConfigurationProperties({SwaggerConfigPropBean.class})
public class SwaggerConfiguration implements ApplicationContextAware {
    
    @Autowired
    private SwaggerConfigPropBean swaggerConfigPropBean;
    
    private BeanDefinitionRegistry beanFactory;
    
    @PostConstruct
    public void init() {
        TypeResolver typeResolver = new TypeResolver();
        final ResolvedType dateTime = typeResolver.resolve(LocalDateTime.class);
        final ResolvedType stringType = typeResolver.resolve(LocalDateTimeString.class);
        swaggerConfigPropBean.getGroups().forEach((key, paths) -> {
            String name = paths[0];
            List<Predicate<String>> pathList = new ArrayList<>();
            
            for (int i = 1; i < paths.length; i++) {
                pathList.add(regex(paths[i]));
            }
            
            Predicate<String> path = or(pathList.toArray(new Predicate[pathList.size()]));
            Docket docket = new Docket(DocumentationType.SWAGGER_2)
                    .groupName(name == null ? key : name)
                    .alternateTypeRules(new AlternateTypeRule(dateTime, stringType)).select()
                                                                   .apis(RequestHandlerSelectors.withClassAnnotation(RestController.class))
                                                                   .paths(path).build().apiInfo(apiInfo());
            
            addDocket("swagger-api-" + key, docket);
        });
    }
    
    private ApiInfo apiInfo() {
        return new ApiInfoBuilder().title(swaggerConfigPropBean.getTitle()).description(swaggerConfigPropBean.getDescription())
                                   .contact(swaggerConfigPropBean.getContact()).version(swaggerConfigPropBean.getVersion()).build();
    }
    
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.beanFactory = (BeanDefinitionRegistry) ((ConfigurableApplicationContext) applicationContext).getBeanFactory();
    }
    
    
    private void addDocket(String key, Docket object) {
        BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(Docket.class, () -> object);
        BeanDefinition beanDefinition = beanDefinitionBuilder.getRawBeanDefinition();
        this.beanFactory.registerBeanDefinition(key, beanDefinition);
    }
    
}
